package nachos.threads;

import nachos.machine.*;

/**
 * An implementation of condition variables that disables interrupt()s for
 * synchronization.
 *
 * <p>
 * You must implement this.
 *
 * @see	nachos.threads.Condition
 */
public class Condition2 {
	/**
	 * Allocate a new condition variable.
	 *
	 * @param	conditionLock	the lock associated with this condition
	 *				variable. The current thread must hold this
	 *				lock whenever it uses <tt>sleep()</tt>,
	 *				<tt>wake()</tt>, or <tt>wakeAll()</tt>.
	 */
	public Condition2(Lock conditionLock) {
		this.conditionLock = conditionLock;
	}

	/**
	 * Atomically release the associated lock and go to sleep on this condition
	 * variable until another thread wakes it using <tt>wake()</tt>. The
	 * current thread must hold the associated lock. The thread will
	 * automatically reacquire the lock before <tt>sleep()</tt> returns.
	 */
	public void sleep() {
		Lib.assertTrue(conditionLock.isHeldByCurrentThread());

		conditionLock.release();
		
		boolean intStatus = Machine.interrupt().disable();	// 
	    waitQueue.waitForAccess(KThread.currentThread());	// code added
		KThread.sleep();									//
		Machine.interrupt().restore(intStatus);				//

		conditionLock.acquire();
	}

	/**
	 * Wake up at most one thread sleeping on this condition variable. The
	 * current thread must hold the associated lock.
	 */
	public void wake() {
		Lib.assertTrue(conditionLock.isHeldByCurrentThread());
		
		boolean intStatus = Machine.interrupt().disable();	//
		KThread thread = waitQueue.nextThread();			//
		if (thread != null)									// code added
		    thread.ready();									//
		Machine.interrupt().restore(intStatus);				//
	}

	/**
	 * Wake up all threads sleeping on this condition variable. The current
	 * thread must hold the associated lock.
	 */
	public void wakeAll() {
		Lib.assertTrue(conditionLock.isHeldByCurrentThread());
		
		boolean intStatus = Machine.interrupt().disable();	//
		KThread thread = waitQueue.nextThread();			//
		while (thread != null) {							//
			thread.ready();									// code added
			thread = waitQueue.nextThread();				//
		}													//
		Machine.interrupt().restore(intStatus);				//
	}
	
	
	static void selfTest () {

        final Lock lock = new Lock();
        final Condition2 cond = new Condition2(lock);
        
        KThread t1 = new KThread(new Runnable() {
	        public void run() {
	            lock.acquire();
	            for (int k=0; k<10000; k++)
	                if (k % 1000 == 0)
	                    System.out.println("at " + k / 1000);
	                else if (k == 499) {
	                    System.out.println("putting (t1) to sleep");
	                    cond.sleep();
	                }
	            lock.release();
	        }
	    });
        
        KThread t2 = new KThread(new Runnable() {
            public void run() {
                lock.acquire();
                System.out.println("putting t2 to sleep");
                cond.sleep();
                lock.release();
            }
        });
        t2.setName("T2");
        
        KThread t3 = new KThread(new Runnable() {
            public void run() {
                lock.acquire();
                System.out.println("putting t3 to sleep");
                cond.sleep();
                lock.release();
            }
        });
        t3.setName("T3");
        
        t1.setName("T1");
        
        System.out.println("**STARTING CONDITION2 TESTS***");
        t1.fork();
        while (t1.getStatus() != KThread.statusBlocked)
            KThread.yield();
        // t1.join();   ----> this causes deadlock
        /**
         * add breakpoint here. check debugger to make sure lock is not held by
         * anyone. check to make sure that t1 exists in cond's wait queue
         */
        lock.acquire();
        cond.wake();
        /**
         * add breakpoint. check for:
         * 1) T1 is on the ready queue
         * 2) status is statusReady
         * 3) currentThread still holds lock
         */
        lock.release();
        t1.join();
        
        
        t2.fork();
        t3.fork();
        while (t2.getStatus() != KThread.statusBlocked ||
                t3.getStatus() != KThread.statusBlocked)
            KThread.yield();
        lock.acquire();
        cond.wakeAll();     // threads t2+t3 should go from sleeping to ready
        lock.release();
        t2.join();          // this thread will join with these provided
        t3.join();          // wakeAll() worked properly
        System.out.println("t2 & t3 woken up by wakeAll() properly");
        System.out.println("****CONDITION2 TESTS COMPLETED*****\n");
	}
	

	private Lock conditionLock;
	private ThreadQueue waitQueue =
		ThreadedKernel.scheduler.newThreadQueue(false);
}
